package com.nulldev.util.networking.npacket4.iprotocols.ncryptx;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import com.nulldev.util.VariableAPI.RandomUtil;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.networking.npacket4.nPacket;
import com.nulldev.util.networking.npacket4.extensions.PacketExtension;
import com.nulldev.util.networking.npacket4.iprotocols.ncryptx.methods.Curve25519;
import com.nulldev.util.networking.npacket4.iprotocols.ncryptx.methods.newhope.NewHopeKeyExchange;
import com.nulldev.util.networking.npacket4.iprotocols.ncryptx.methods.newhope.NewHopeKeyPair;
import com.nulldev.util.networking.npacket4.iprotocols.ncryptx.methods.sidh.SidhKeyExchange;
import com.nulldev.util.networking.npacket4.iprotocols.ncryptx.methods.sidh.SidhKeyPair;

public class nCryptKeyShare extends nPacket {

	private static final long serialVersionUID = 4675262687953062921L;
	private KeyExchangeMethod method;
	private byte[] publicKey, privateKey;

	public nCryptKeyShare(final nPacket packet) {
		super("ncryptx.key_share", packet);
	}

	public nCryptKeyShare(final KeyExchangeMethod method, final PacketExtension... exts) {
		super("ncryptx.key_share", exts);
		this.method = method;
	}

	@Override
	public void onGenerate(final DataOutputStream dos) throws IOException {
		dos.writeShort(this.method.id());
		switch (this.method) {
			case NCRYPTX1_INSEC_RNG_KEY_GEN: {
				this.privateKey = RandomUtil.bytes(8);
				dos.writeInt(this.privateKey.length);
				dos.write(this.privateKey);
				break;
			}
			case NCRYPTX1_DHE_CURVE25519: {
				this.privateKey = Curve25519.generateRandomKey();
				this.publicKey = Curve25519.publicKey(this.privateKey);
				dos.writeInt(this.publicKey.length);
				dos.write(this.publicKey);
				break;
			}
			case NCRYPTX1_SIDH_DHE: {
				final SidhKeyExchange kex = new SidhKeyExchange("sidhP503");
				// FIXME: Shouldn't we use ALICE and BOB?
				final SidhKeyPair pair = kex.generateKeyPair(SidhKeyExchange.ALICE);
				final byte[] pub = pair.getPublicKey().serialize();
				final byte[] pri = pair.getPrivateKey().serialize();
				this.publicKey = pub;
				this.privateKey = pri;
				dos.writeInt(this.publicKey.length);
				dos.write(this.publicKey);
				break;
			}
			case NCRYPTX1_NEWHOPE: {
				final NewHopeKeyExchange kex = new NewHopeKeyExchange();
				final NewHopeKeyPair pair = kex.generateKeyPair();
				this.privateKey = pair.getPrivateKey().serialize();
				this.publicKey = pair.getPublicKey().serialize();
				dos.writeInt(this.publicKey.length);
				dos.write(this.publicKey);
				break;
			}
			case SHARED_KEY_EXCHANGE: {
				this.privateKey = RandomUtil.bytes(128);
				dos.writeInt(this.privateKey.length);
				dos.write(this.privateKey);
				break;
			}
			case PKPK_KEY_EXCHANGE:
			default:
				throw new UnsupportedOperationException("Unknown method: " + this.method);
		}
	}

	@Override
	public void onPacket(final DataInputStream dis) throws IOException {
		this.method = KeyExchangeMethod.get(dis.readShort());
		switch (this.method) {
			case SHARED_KEY_EXCHANGE:
			case NCRYPTX1_INSEC_RNG_KEY_GEN: {
				final int keyLength = dis.readInt();
				final byte[] key = new byte[keyLength];
				IOUtils.readFully(dis, key);
				this.privateKey = key;
				break;
			}
			case NCRYPTX1_SIDH_DHE:
			case NCRYPTX1_NEWHOPE:
			case NCRYPTX1_DHE_CURVE25519: {
				final int keyLength = dis.readInt();
				final byte[] key = new byte[keyLength];
				IOUtils.readFully(dis, key);
				this.publicKey = key;
				break;
			}
			case PKPK_KEY_EXCHANGE:
			default:
				throw new UnsupportedOperationException("Unknown method: " + this.method);
		}
	}

	public byte[] publicKey() {
		return this.publicKey;
	}

	public byte[] privateKey() {
		return this.privateKey;
	}

	public KeyExchangeMethod getMethod() {
		return this.method;
	}
}
